package simpledb.server;

import simpledb.file.*;
import simpledb.tx.Transaction;
import simpledb.buffer.*;

/*test*/
import simpledb.record.*;
import static simpledb.file.Page.*;
import static simpledb.sql.Types.*;

import java.util.ArrayList;
import java.util.Random;

public class SimpleDB {
	public static int BUFFER_SIZE = 3;
	/**************************/
	/* 			test		  */
	/**************************/
	public static boolean FILE_TEST = false;
	public static int BLOCK_NR;
	
	private static FileMgr     fm;
	private static BufferMgr   bm;
	
	
	public static void init(String filename) {
		fm    = new FileMgr(filename);
		bm    = new BufferMgr(BUFFER_SIZE);
		
		boolean isnew = fm.isNew();
		if (isnew)
			System.out.println("creating new database");
		else {
			System.out.println("recovering existing database");
		}
	}
	
	public static FileMgr     fileMgr()   { return fm; }
	public static BufferMgr   bufferMgr() { return bm; }
	
	

	/**************************/
	/* 			test		  */
	/**************************/
	public static void test(){
		testCreateFile();
//		testAddBuffer();
//		testFileMgr();
//		testTruncate();
//		testLRU();
//		testRecPage();
		testRecFile();
	}
	/**
	 * - initialize a record page with specific schema, base of records
	 */
	private static void testRecFile(){
		Transaction tx = new Transaction();
		Schema sch = new Schema();
		sch.addStringField("str", 16);
		sch.addIntField("int");
		TableInfo ti = new TableInfo("testTable", sch, 0);
		RecordMgr rf = new RecordMgr(ti,tx);
		int number = 10;
		long timestamp = System.currentTimeMillis();
		
		// insert records
		for(int i=1;i <= number; i++){
			rf.insert();
			rf.setInt("int", i);
			rf.setString("str", "My name is Conan");
//			System.out.println(i);
		}
		bm.flushAll(1);
//		System.out.println("Time cost: " + (System.currentTimeMillis() - timestamp));
		
		listAll(rf);
		
		// delete all the record at even position
		rf.beforeFirst();
		for(int i=1;i <= number; i++){
			rf.next();
			if(i%2 != 0) continue;
			rf.delete();
		}
		bm.flushAll(1);
		
		listAll(rf);

		rf.beforeFirst();
		for(int i=1;i <= number; i++){
			if(i%2!=0) continue;
			rf.insert();
			rf.setInt("int", i);
			rf.setString("str", "Hello world!");
		}
		bm.flushAll(1);
		
		listAll(rf);
		
		bm.testShowState(fm.size());
	}
	private static void testRecPage(){
		Transaction tx = new Transaction();
		Schema sch = new Schema();
		sch.addStringField("str", 4);
		sch.addIntField("int");
		TableInfo ti = new TableInfo("testTable", sch, 0);
		RecordPage recPage = new RecordPage(0, ti, tx);
		
		System.out.println("Get int: " + recPage.getInt("int"));
	}
	
	private static void testLRU(){
		int blk = 0, tmp;
		Random rd = new Random();
		double r;
		ArrayList<Buffer> unpins = new ArrayList<Buffer>();
		boolean detail = true;
		
		if(detail) System.out.println(BLOCK_NR + " blocks in the file");
		
		for(int i=0; i<100000; i++){
			do{
				r = rd.nextFloat();
				r = r*r;
				if(rd.nextBoolean()) r = -r;
				tmp = blk + (int)(r*BLOCK_NR);
			}while(tmp<0 || tmp>=BLOCK_NR);
			blk=tmp;
//			blk=BLOCK_SIZE;
			if(detail) System.out.print("Block " + blk +" Pin");
			Buffer buff = bm.pin(blk);
			if(detail) System.out.println(" Content: " + buff.getInt(0));
			buff.setInt(0, buff.getInt(0)+BLOCK_NR, 1, 0);
			unpins.add(buff);
			do{
				for(Buffer b: unpins){
					if(rd.nextBoolean()){
						if(detail) System.out.println("Block " + b.block() +" Unpin");
						bm.unpin(b);
						unpins.remove(b);
						break;
					}
				}
			}while(rd.nextBoolean());
			
			bm.testShowState(BLOCK_NR);
			try{
				Thread.sleep(500);
			}catch(Exception e){}
		}
	}
	private static void testTruncate(){
		Buffer b = bm.pin(BLOCK_NR-1);
		String str;
		if(bm.unpinFree(b)) str = "Truncate is over";
		else str = "Truncate failed";
		System.out.println(str);
	}
	private static void testFileMgr(){
		FILE_TEST = true;
		Page p = new Page();
		int blk = 0;
		p.setString(0*INT_SIZE, "hello world!");
		p.write(blk);
		p.read(0);
		System.out.println(p.getString(0));
	}
	private static void testAddBuffer(){
		final int size = BLOCK_NR;
		Buffer b;
		TestAddBufferFormatter f;
		for(int i=0; i < size; i++){
			f = new TestAddBufferFormatter(i);
			b = bm.pinNew(f);
			bm.unpin(b);
		}
	}
	private static void testCreateFile(){
		int i=0;
		boolean isnew=false;
		while(!isnew){
			fm    = new FileMgr("test"+i);
			bm    = new BufferMgr(BUFFER_SIZE);
			isnew = fm.isNew();
			i++;
		}
	}
	private static void listAll(RecordMgr rf){
		int i = 1;
		rf.beforeFirst();
		while(rf.next()){
//		if(i != rf.getInt("int") || !rf.getString("str").equals("My name is Conan"))
			System.out.println("Record "+(i++)+": str="+rf.getString("str")+", int="+rf.getInt("int"));
		}
	}
}

/**************************/
/* 			test		  */
/**************************/
class TestAddBufferFormatter implements PageFormatter{
	int id;
	TestAddBufferFormatter(int id){
		this.id = id;
	}
	public void format(Page p) {
		for(int pos=0; pos<BLOCK_SIZE; pos+=INT_SIZE)
			p.setInt(pos, id);				
	}
} 